/*
* This software is licensed under the GNU General Public License, version 2
* You may copy, distribute and modify the software as long as you track changes/dates of in source files and keep all modifications under GPL. You can distribute your application using a GPL library commercially, but you must also provide the source code.

* DNNspot Software (http://www.dnnspot.com)
* Copyright (C) 2013 Atriage Software LLC
* Authors: Kevin Southworth, Matthew Hall, Ryan Doom

* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

* Full license viewable here: http://www.gnu.org/licenses/gpl-2.0.txt
*/

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Services;
using System.Web.Services.Description;
using DNNspot.ChatRoom.Common;
using DNNspot.ChatRoom.DataModel;
using EntitySpaces.Interfaces;
using Message=DNNspot.ChatRoom.DataModel.Message;

namespace DNNspot.ChatRoom
{
    /// <summary>
    /// Summary description for $codebehindclassname$
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class AjaxHandler : IHttpHandler
    {
        // PRIVATE MEMBERS #######################################################

        private HttpContext _context;
        
        // PROPERTIES ############################################################

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        // EVENTS ################################################################

        public void ProcessRequest(HttpContext context)
        {
            InitializeEntitySpaces();

            _context = context;

            HttpRequest request = context.Request;
            HttpResponse response = context.Response;

            response.ContentType = "text/plain";

            //--------------------------------------------
            // DO MESSAGE
            var output = string.Empty;

            switch (request.Params[PostParams.Action])
            {
                case HandlerActions.AddUser:
                    output = AddUser(request.Params);
                    break;
                case HandlerActions.GetRoomState:
                    output = GetRoomState(request.Params);
                    break;
                case HandlerActions.Ping:
                    output = Ping(request.Params);
                    break;
                case HandlerActions.PostMessage:
                    output = PostMessage(request.Params);
                    break;
                case HandlerActions.RemoveUser:
                    output = RemoveUser(request.Params);
                    break;
                default:
                    break;
            }
            
            response.Write(output);
            response.Flush();
        }
        
        // PRIVATE METHODS #######################################################

        private static string AddUser(NameValueCollection parms)
        {
            var output = string.Empty;

            var portalId = Convert.ToInt32(parms[PostParams.PortalId]);
            var moduleId = Convert.ToInt32(parms[PostParams.ModuleId]);
            var userId = Convert.ToInt32(parms[PostParams.UserId]);
            var userHandle = parms[PostParams.UserHandle];
            var userIp = parms[PostParams.UserIp];
            int? maxAllowedUsers = null;
            if(parms[PostParams.MaxAllowedUsers] != null)
            {
                maxAllowedUsers = Convert.ToInt32(parms[PostParams.MaxAllowedUsers]);
            }

            var regex = new Regex("^[A-Za-z0-9_-]{2,15}$");

            if(!regex.Match(userHandle).Success)
            {
                output = Newtonsoft.Json.JsonConvert.SerializeObject(new { success = false, error = "Invalid username. Letters, numbers, underscores, and hyphens only." });
                //output = "{ \"success\": false, \"error\": \"Invalid username. Letters, numbers, underscores, and hyphens only.\" }";
            }
            else if (User.UserExists(portalId, moduleId, userId, userHandle))
            {
                output = Newtonsoft.Json.JsonConvert.SerializeObject(new { succes = false, error = "Username already in use." });
                //output = "{ \"success\": false, \"error\": \"Username already in use.\" }";
            } 
            else
            {
                bool addUser = true;
                if(maxAllowedUsers.HasValue && (UserCollection.GetUserCount() >= maxAllowedUsers.Value))
                {
                    addUser = false;
                    output = Newtonsoft.Json.JsonConvert.SerializeObject(new { success = false, error = "You have reached the max user limit for this module." });
                    //output = "{\"success\": false, \"error\": \"You have reached the max user limit for this module.\" }"; 
                }

                if(addUser)
                {
                    User.AddUser(portalId, moduleId, userId, userHandle, userIp);
                    output = Newtonsoft.Json.JsonConvert.SerializeObject(new { success = true, MessageId = Message.GetLastMessageId(portalId, moduleId) });
                    //output = string.Format("{{\"success\": true, \"MessageId\": {0} }}", Message.GetLastMessageId(portalId, moduleId));
                }
            }

            return output;
        }

        private static string GetRoomState(NameValueCollection parms)
        {
            var portalId = Convert.ToInt32(parms[PostParams.PortalId]);
            var moduleId = Convert.ToInt32(parms[PostParams.ModuleId]);
            var messageId = Convert.ToInt32(parms[PostParams.MessageId]);
            var userHandle = parms[PostParams.UserHandle];
            
            const int sleepTime = 500;
            const int maxLoadTime = 60000;

            // Logged in and waiting for a message
            var messages = new List<Message>();
            var i = 0;

            // Comet implementation
            while (true)
            {
                messages = DataModel.MessageCollection.GetMessagesAfterMessageId(portalId, moduleId, messageId, userHandle);

                if(messages.Count > 0 || i >= maxLoadTime) break;

                System.Threading.Thread.Sleep(sleepTime);
                i += sleepTime;
            }

            // Get back room state: messages and users
            var messageInfos = messages.ConvertAll(
                m => new MessageInfo((int)m.MessageId, m.UserHandle, m.Body, (DateTime)m.CreatedDate));

            var users = UserCollection.GetUsers(portalId, moduleId).ConvertAll(
                u => new UserInfo((int)u.UserId, u.UserHandle, u.Ip, Convert.ToBoolean(u.IsAway), u.AwayMessage));

            // Create new RoomState object and conver to JSON
            var roomState = new RoomState() {Messages = messageInfos, Users = users};
            var output = Newtonsoft.Json.JsonConvert.SerializeObject(roomState);

            return output;
        }

        private static void InitializeEntitySpaces()
        {
            if (esConfigSettings.ConnectionInfo.Default != "SiteSqlServer")
            {
                esConfigSettings connectionInfoSettings = esConfigSettings.ConnectionInfo;
                foreach (esConnectionElement connection in connectionInfoSettings.Connections)
                {
                    //if there is a SiteSqlServer in es connections set it default
                    if (connection.Name == "SiteSqlServer")
                    {
                        esConfigSettings.ConnectionInfo.Default = connection.Name;
                        return;
                    }
                }

                //no SiteSqlServer found grab dnn cnn string and create
                string dnnConnection = ConfigurationManager.ConnectionStrings["SiteSqlServer"].ConnectionString;

                // Manually register a connection
                esConnectionElement conn = new esConnectionElement();
                conn.ConnectionString = dnnConnection;
                conn.Name = "SiteSqlServer";
                conn.Provider = "EntitySpaces.SqlClientProvider";
                conn.ProviderClass = "DataProvider";
                conn.SqlAccessType = esSqlAccessType.DynamicSQL;
                conn.ProviderMetadataKey = "esDefault";
                conn.DatabaseVersion = "2005";

                // Assign the Default Connection
                esConfigSettings.ConnectionInfo.Connections.Add(conn);
                esConfigSettings.ConnectionInfo.Default = "SiteSqlServer";

                // Register the Loader
                esProviderFactory.Factory = new EntitySpaces.Loader.esDataProviderFactory();
            }
        }

        private static string Ping(NameValueCollection parms)
        {
            var portalId = Convert.ToInt32(parms[PostParams.PortalId]);
            var moduleId = Convert.ToInt32(parms[PostParams.ModuleId]);
            var userHandle = parms[PostParams.UserHandle];

            var age = new TimeSpan(0, 1, 0);

            User.SetModifiedDate(portalId, moduleId, userHandle);
            UserCollection.RemoveOldUsers(portalId, moduleId, age);

            return string.Empty;
        }

        private string PostMessage(NameValueCollection parms)
        {
            var output = string.Empty;

            var portalId = Convert.ToInt32(parms[PostParams.PortalId]);
            var moduleId = Convert.ToInt32(parms[PostParams.ModuleId]);
            var userId = Convert.ToInt32(parms[PostParams.UserId]);
            var userHandle = parms[PostParams.UserHandle];
            var userIp = parms[PostParams.UserIp];
            var body = parms[PostParams.Body];

            var targetUserHandle = string.Empty;

            body = _context.Server.HtmlEncode(body);

            //////////////////////////////////////////////
            // COMMANDS - this is not extracted into
            // its own method because we need t be able
            // to return a message for commands like quit

            // /me {action}
            if (body.StartsWith(Commands.Me, true, null) && body.Length > Commands.Me.Length)
                body = string.Format("<em class='me'>{0}</em>", body.Remove(0, Commands.Me.Length));

            // /notice {message}
            if (body.StartsWith(Commands.Notice, true, null) && body.Length > Commands.Notice.Length)
            {
                body = string.Format("<span class='notice'>{0}</span>", body.Remove(0, Commands.Notice.Length));
                userHandle = "";
            }

            // /msg {userHandle} {message}
            if (body.StartsWith(Commands.Msg, true, null) && body.Length > Commands.Msg.Length)
            {
                body = body.Remove(0, Commands.Msg.Length).Trim();
                targetUserHandle = body.Substring(0, body.IndexOf(' '));
                body = string.Format("<span class='msg'>{0}</span>", body.Remove(0, body.IndexOf(' ')).Trim());
            }

            // /away [{message}]
            if (body.StartsWith(Commands.Away, true, null) && body.Length > Commands.Away.Length)
            {
                var reason = body.Remove(0, Commands.Away.Length).Trim();

                User.SetAwayStatus(portalId, moduleId, userHandle, true, reason);
                body = string.Format("<span class='notice'>{0} is away - {1}</span>", userHandle, reason);
                userHandle = "";
            } 
            else if (body.ToLower().Equals(Commands.Away.Trim()) && User.GetAwayStatus(portalId, moduleId, userHandle))
            {
                User.SetAwayStatus(portalId, moduleId, userHandle, false, "");
                body = string.Format("<span class='notice'>{0} is no longer away</span>", userHandle);
                userHandle = "";
            }

            // /quit [{message}]
            if (body.StartsWith(Commands.Quit.Trim(), true, null))
            {
                User.RemoveUser(portalId, moduleId, userHandle);

                var msg = string.Empty;
                if (body.Length > Commands.Quit.Length)
                    msg = string.Format(" - {0}", body.Remove(0, Commands.Quit.Length));

                body = string.Format("<span class='notice'>{0} left the chat room{1}</span>", userHandle, msg);
                userHandle = "";

                output = Newtonsoft.Json.JsonConvert.SerializeObject(new { logout = true });
                //output = "{\"logout\": true}";
            }

            //////////////////////////////////////////////
            // post message

            if(!string.IsNullOrEmpty(userHandle) && User.GetUser(portalId, moduleId, userHandle) == null)
                    User.AddUser(portalId, moduleId, userId, userHandle, userIp);

            Message.AddMessage(portalId, moduleId, userId, userHandle, userIp, targetUserHandle, body);

            return output;
        }

        private static string RemoveUser(NameValueCollection parms)
        {
            var portalId = Convert.ToInt32(parms[PostParams.PortalId]);
            var moduleId = Convert.ToInt32(parms[PostParams.ModuleId]);
            var userHandle = parms[PostParams.UserHandle];

            User.RemoveUser(portalId, moduleId, userHandle);

            return string.Empty;
        }
    }
}
